/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.data.redis.factory;

import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.ClientType;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.tianyun.cloud.config.console.ConsoleBean;
import org.tianyun.cloud.data.redis.configuration.RedisContainerProperties;

import java.util.Properties;

/**
 * 自定义RedisConnectionFactory
 *
 * @auther ebert_chan
 */
public class SpringRedisConnectionFactory implements RedisConnectionFactory, ConsoleBean, DisposableBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringRedisConnectionFactory.class);

    private RedisContainerProperties properties;

    private ClientResources clientResources;

    private ClientType clientType;

    private LettuceConnectionFactory lettuceConnectionFactory;

    private JedisConnectionFactory jedisConnectionFactory;

    private boolean isTargetClientResource;

    private Properties startupArguments;

    public SpringRedisConnectionFactory(RedisContainerProperties properties, ObjectProvider<ClientResources> clientResourceProvider) {
        this.properties = properties;

        if (clientResourceProvider != null) {
            setClientResources(clientResourceProvider.getIfAvailable());
        }
        isTargetClientResource = clientResources != null;
        if (!isTargetClientResource) {
            clientResources = DefaultClientResources.create();
        }

        this.configure(this.properties);
    }

    public void setClientResources(ClientResources clientResources) {
        this.clientResources = clientResources;
    }

    private void configure(RedisContainerProperties properties) {
        clientType = properties.getClientType();
        startupArguments = new Properties();

        RedisConnectionFactoryBuilder builder = RedisConnectionFactoryBuilder.factory();
        builder.setProperties(properties);

        LOGGER.info("----------------------------------------------------------------------------------------------");
        LOGGER.info("Redis ClientType: {}", clientType.name());

        startupArguments.put("redis.client-type", clientType.name());

        if (builder.getSentinelConfig() != null) {
            LOGGER.info("Redis Sentinel nodes: {}", properties.getSentinel().getNodes() != null ? String.join(",", properties.getSentinel().getNodes()) : null);
            LOGGER.info("Redis Sentinel master: {}", properties.getSentinel().getMaster());
            LOGGER.info("Redis Sentinel username: {}", properties.getUsername());
            LOGGER.info("Redis Sentinel password: ******");
            LOGGER.info("Redis Sentinel database: {}", properties.getDatabase());

            startupArguments.put("redis.sentinel.master", properties.getSentinel().getMaster());
            startupArguments.put("redis.sentinel.nodes", properties.getSentinel().getNodes() != null ? String.join(",", properties.getSentinel().getNodes()) : null);
            startupArguments.put("redis.sentinel.database", properties.getDatabase());
        } else if (builder.getClusterConfiguration() != null) {
            LOGGER.info("Redis Cluster nodes: {}", properties.getCluster().getNodes() != null ? String.join(",", properties.getCluster().getNodes()) : null);
            LOGGER.info("Redis Cluster password: ******");
            LOGGER.info("Redis Cluster maxRedirects: {}", properties.getCluster().getMaxRedirects());

            startupArguments.put("redis.cluster.nodes", properties.getCluster().getNodes() != null ? String.join(",", properties.getCluster().getNodes()) : null);
        } else {
            RedisStandaloneConfiguration redisStandaloneConfiguration = builder.getStandaloneConfig();
            LOGGER.info("Redis Standalone hostName: {}", redisStandaloneConfiguration.getHostName());
            LOGGER.info("Redis Standalone port: {}", redisStandaloneConfiguration.getPort());
            LOGGER.info("Redis Standalone username: {}", redisStandaloneConfiguration.getUsername());
            LOGGER.info("Redis Standalone password: ******");
            LOGGER.info("Redis Standalone database: {}", properties.getDatabase());

            startupArguments.put("redis.standalone.master", redisStandaloneConfiguration.getHostName());
            startupArguments.put("redis.standalone.port", redisStandaloneConfiguration.getPort());
            startupArguments.put("redis.standalone.database", properties.getDatabase());
        }
        LOGGER.info("----------------------------------------------------------------------------------------------");

        if (ClientType.JEDIS.equals(clientType)) {
            jedisConnectionFactory = (JedisConnectionFactory) builder.createRedisConnectionFactory();
        } else {
            builder.setClientResources(clientResources);
            lettuceConnectionFactory = (LettuceConnectionFactory) builder.createRedisConnectionFactory();
        }

    }

    @Override
    public Properties startupArguments() {
        return this.startupArguments;
    }

    private RedisConnectionFactory getTargetRedisConnectionFactory() {
        if (ClientType.JEDIS.equals(clientType)) {
            return jedisConnectionFactory;
        }

        return lettuceConnectionFactory;
    }

    /*
     * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException)
     */
    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        return getTargetRedisConnectionFactory().translateExceptionIfPossible(ex);
    }

    /*
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection()
     */
    @Override
    public RedisConnection getConnection() {
        return getTargetRedisConnectionFactory().getConnection();
    }

    /*
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getClusterConnection()
     */
    @Override
    public RedisClusterConnection getClusterConnection() {
        return getTargetRedisConnectionFactory().getClusterConnection();
    }

    /*
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConvertPipelineAndTxResults()
     */
    @Override
    public boolean getConvertPipelineAndTxResults() {
        return getTargetRedisConnectionFactory().getConvertPipelineAndTxResults();
    }

    /*
     * @see org.springframework.data.redis.connection.RedisConnectionFactory#getSentinelConnection()
     */
    @Override
    public RedisSentinelConnection getSentinelConnection() {
        return getTargetRedisConnectionFactory().getSentinelConnection();
    }

    protected ReactiveRedisConnectionFactory getReactiveRedisConnectionFactory() {
        RedisConnectionFactory redisConnectionFactory = getTargetRedisConnectionFactory();
        if (redisConnectionFactory instanceof ReactiveRedisConnectionFactory) {
            return (ReactiveRedisConnectionFactory) redisConnectionFactory;
        }
        return null;
    }

    /*
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    @Override
    public void destroy() throws Exception {
        if (!isTargetClientResource) {
            clientResources.shutdown();
        }
        if (lettuceConnectionFactory != null) {
            lettuceConnectionFactory.destroy();
        }
        if (jedisConnectionFactory != null) {
            jedisConnectionFactory.destroy();
        }
    }

}
